#include "precompiled/pchkrayserkraw.h"
#include "kraw/krawd3d9material.h"
#include "kraw/kraw3dmodel.h"

#include "kernel/nkernelserver.h"
#include "kernel/ncmdprotonativecpp.h"

#include <d3dx9.h>

//-----------------------------------------------------------------------------

extern LPDIRECT3DDEVICE9 g_pd3d9Device;

//-----------------------------------------------------------------------------

class KrawD3D9Model
{
public:

    KrawD3D9Model();
    ~KrawD3D9Model();

    void Destroy();

    HRESULT LoadVertexDeclaration( void* pVertexDecl );
    HRESULT LoadVertexBuffer( uint nVertices, uint vertexSize, float* pVertexData );
    HRESULT LoadIndexBuffer( uint nIndices, uint indexSize, uint* indexData );

    LPDIRECT3DVERTEXDECLARATION9 pVertexDecl;
    LPDIRECT3DVERTEXBUFFER9 pVertexBuffer;
    LPDIRECT3DINDEXBUFFER9 pIndexBuffer;

    D3DPRIMITIVETYPE primitiveType;
    uint nVertices;
    uint nIndices;
    UINT stride;
};

//-----------------------------------------------------------------------------

KrawD3D9Model::KrawD3D9Model() :
    pVertexBuffer(0),
    pIndexBuffer(0),
    pVertexDecl(0)
{
}

//-----------------------------------------------------------------------------

KrawD3D9Model::~KrawD3D9Model()
{
    this->Destroy();
}

//-----------------------------------------------------------------------------

HRESULT KrawD3D9Model::LoadVertexDeclaration( void* pVertexDeclData )
{
    n_assert( pVertexDeclData );

    //create vertex declaration
    //IDirect3DVertexDeclaration9* d3d9vdecl;
    //TODO- look for a compatible vertex declaration
    //if ( !FindVertexDeclaration( pVertexDecl ) )

    D3DVERTEXELEMENT9* vertexdecl = (D3DVERTEXELEMENT9*) pVertexDeclData;
    if ( FAILED( g_pd3d9Device->CreateVertexDeclaration( vertexdecl, &this->pVertexDecl ) ) )
        return E_FAIL;
    return S_OK;
}

//-----------------------------------------------------------------------------

HRESULT KrawD3D9Model::LoadVertexBuffer( uint nVertices, uint vertexSize, float* pVertexData )
{
    //TODO- 
    DWORD d3dUsage = D3DUSAGE_WRITEONLY;
    D3DPOOL d3dPool = D3DPOOL_MANAGED;
    //TODO- dynamic vertex buffer (ReadWrite)
    //TODO- Patches, PointSprite
    //TODO- software vertex processing

    this->primitiveType = D3DPT_TRIANGLELIST;
    this->stride = vertexSize;
    this->nVertices = nVertices;

    // create the vertex buffer
    if ( FAILED( g_pd3d9Device->CreateVertexBuffer( nVertices * vertexSize,
                                                   d3dUsage,
                                                   0,
                                                   d3dPool,
                                                   &(this->pVertexBuffer),
                                                   NULL ) ) )
        return E_FAIL;

    VOID* pVertices;
    if ( SUCCEEDED( this->pVertexBuffer->Lock( 0, 0, &pVertices, 0 /*d3dVBLockFlags*/ ) ) )
    {
        memcpy( pVertices, pVertexData, nVertices * vertexSize );
        this->pVertexBuffer->Unlock();
    }

    return S_OK;
}

//-----------------------------------------------------------------------------

HRESULT KrawD3D9Model::LoadIndexBuffer( uint nIndices, uint indexSize, uint* pIndexData )
{
    n_assert( pIndexData );

    DWORD d3dUsage = D3DUSAGE_WRITEONLY;
    D3DPOOL d3dPool = D3DPOOL_MANAGED;
    D3DFORMAT d3dFormat = indexSize == sizeof(WORD)  ? D3DFMT_INDEX16 : D3DFMT_INDEX32;
    //TODO- dynamic index buffer (ReadWrite, WriteOnly)
    //TODO- Patches, PointSprite
    //TODO- software vertex processing

    this->nIndices = nIndices;

    HRESULT hr = g_pd3d9Device->CreateIndexBuffer(
            nIndices * indexSize,
            d3dUsage,
            d3dFormat,
            d3dPool,
            &(this->pIndexBuffer),
            NULL);

    VOID* pIndices;
    if ( SUCCEEDED( this->pIndexBuffer->Lock( 0, 0, &pIndices, 0 /*d3dIBLockFlags*/ ) ) )
    {
        memcpy( pIndices, pIndexData, nIndices * indexSize );
        this->pIndexBuffer->Unlock();
    }
    

    return hr;
}

//-----------------------------------------------------------------------------

void KrawD3D9Model::Destroy()
{
    if ( this->pVertexDecl )
    {
        this->pVertexDecl->Release();
        this->pVertexDecl = 0;
    }
    if ( this->pVertexBuffer )
    {
        this->pVertexBuffer->Release();
        this->pVertexBuffer = 0;
    }
    if ( this->pIndexBuffer )
    {
        this->pIndexBuffer->Release();
        this->pIndexBuffer = 0;
    }
}

//-----------------------------------------------------------------------------

KrawD3D9Material::KrawD3D9Material()
{
}

//-----------------------------------------------------------------------------

KrawD3D9Material::~KrawD3D9Material()
{
    this->Destroy();
}

//-----------------------------------------------------------------------------

bool KrawD3D9Material::Init()
{
    return Kraw3DMaterial::Init();
}

//-----------------------------------------------------------------------------

void KrawD3D9Material::Destroy()
{
    //Kraw3DMaterial::Destroy();
}

//-----------------------------------------------------------------------------

bool KrawD3D9Material::InitVertexDeclaration( Kraw3DModel* pModel, void** ppVertexDecl )
{
    if ( pModel->streams.Empty() ) return false;

    D3DVERTEXELEMENT9* decl = n_new_array( D3DVERTEXELEMENT9, Kraw3DModel::NumVertexFlags );
    int curOffset  = 0;
    int curIndex = 0;
    for ( int index = 0; index < pModel->streams.Size(); ++index )
    {
        Kraw3DModel::Stream& stream( pModel->streams[index] );

        switch ( stream.size )
        {
        case 1:
            decl[curIndex].Type = D3DDECLTYPE_FLOAT1;
            break;
        case 2:
            decl[curIndex].Type = D3DDECLTYPE_FLOAT2;
            break;
        case 3:
            decl[curIndex].Type = D3DDECLTYPE_FLOAT3;
            break;
        case 4:
            decl[curIndex].Type = D3DDECLTYPE_FLOAT4;
            break;
        default:
            continue;
        }

        decl[curIndex].Stream = 0;
        decl[curIndex].Offset = static_cast<WORD>( curOffset );
        decl[curIndex].Method = D3DDECLMETHOD_DEFAULT;

        switch ( stream.type )
        {
        case Kraw3DModel::Coord:
            decl[curIndex].Usage      = D3DDECLUSAGE_POSITION;
            decl[curIndex].UsageIndex = 0;
            break;
        case Kraw3DModel::Normal:
            decl[curIndex].Usage      = D3DDECLUSAGE_NORMAL;
            decl[curIndex].UsageIndex = 0;
            break;
        case Kraw3DModel::Tangent:
            decl[curIndex].Usage      = D3DDECLUSAGE_TANGENT;
            decl[curIndex].UsageIndex = 0;
            break;
        case Kraw3DModel::Binormal:
            decl[curIndex].Usage      = D3DDECLUSAGE_BINORMAL;
            decl[curIndex].UsageIndex = 0;
            break;
        case Kraw3DModel::Diffuse:
            decl[curIndex].Usage      = D3DDECLUSAGE_COLOR;
            decl[curIndex].UsageIndex = 0;
            break;
        case Kraw3DModel::TexCoord0:
            decl[curIndex].Usage      = D3DDECLUSAGE_TEXCOORD;
            decl[curIndex].UsageIndex = 0;
            break;
        case Kraw3DModel::TexCoord1:
            decl[curIndex].Usage      = D3DDECLUSAGE_TEXCOORD;
            decl[curIndex].UsageIndex = 1;
            break;
        case Kraw3DModel::TexCoord2:
            decl[curIndex].Usage      = D3DDECLUSAGE_TEXCOORD;
            decl[curIndex].UsageIndex = 2;
            break;
        case Kraw3DModel::TexCoord3:
            decl[curIndex].Usage      = D3DDECLUSAGE_TEXCOORD;
            decl[curIndex].UsageIndex = 3;
            break;
        case Kraw3DModel::Weights:
            decl[curIndex].Usage      = D3DDECLUSAGE_BLENDWEIGHT;
            decl[curIndex].UsageIndex = 0;
            break;
        case Kraw3DModel::JIndices:
            decl[curIndex].Usage      = D3DDECLUSAGE_BLENDINDICES;
            decl[curIndex].UsageIndex = 0;
            break;
        }

        curOffset += stream.size * sizeof(float);
        curIndex++;
    }

    //add vertex declaration terminator
    decl[curIndex].Stream = 0xff;
    decl[curIndex].Offset = 0;
    decl[curIndex].Type   = D3DDECLTYPE_UNUSED;
    decl[curIndex].Method = 0;
    decl[curIndex].Usage  = 0;
    decl[curIndex].UsageIndex = 0;

    *ppVertexDecl = (void*) decl;

    return true;
}

//-----------------------------------------------------------------------------

bool KrawD3D9Material::ExportVertices( Kraw3DModel* pModel, uint& vertexSize, float*& pVertexData )
{
    if ( !pModel->IsValid() ) return false;

    //compute vertex size (in bytes)
    uint nFloats = 0;
    for ( int index = 0; index < pModel->streams.Size(); ++index )
        nFloats += pModel->streams[index].size;

    vertexSize = nFloats * sizeof(float);
    pVertexData = (float*) n_new_array( BYTE, pModel->nVertices * vertexSize );

    //convert vertex data
    BYTE* pBufferDst = (BYTE*) pVertexData;
    float* pVertexSrc = 0;
    for ( int index = 0; index < pModel->streams.Size(); ++index )
    {
        bool found = false;
        switch ( pModel->streams[index].type )
        {
        case Kraw3DModel::Coord:
            found = pModel->GetStreamData( Kraw3DModel::Coord, &pVertexSrc );
            break;
        case Kraw3DModel::Diffuse:
            found = pModel->GetStreamData( Kraw3DModel::Diffuse, &pVertexSrc );
            break;
        case Kraw3DModel::Normal:
            found = pModel->GetStreamData( Kraw3DModel::Normal, &pVertexSrc );
            break;
        default:
            continue;
        }

        FLOAT* pVertexDst = (FLOAT*) pBufferDst;
        uint stride = pModel->streams[index].size * sizeof(float);
        pBufferDst += stride;

        //copy relevant vertex data
        if ( found )
        {
            for ( uint iVertex = 0; iVertex < pModel->nVertices; ++iVertex )
            {
                memcpy( pVertexDst, pVertexSrc, stride );
                pVertexDst += nFloats;
                pVertexSrc += pModel->nComponents;
            }
        }
    }

    return true;
}

//-----------------------------------------------------------------------------

bool KrawD3D9Material::ExportIndices( Kraw3DModel* pModel, uint*& pIndexData )
{
    if ( !pModel->IsValid() ) return false;

    //TODO- support 16-bit indices if they are enough
    memcpy( pIndexData, pModel->pIndices, pModel->nIndices * sizeof(DWORD) );

    return true;
}

//-----------------------------------------------------------------------------

void KrawD3D9Material::Delete( float* pVertexData, uint* pIndexData )
{
    if ( pVertexData )
        n_delete_array( pVertexData );
    if ( pIndexData )
        n_delete_array( pIndexData );
}

//-----------------------------------------------------------------------------

void* KrawD3D9Material::Load( Kraw3DModel* pModel )
{
    n_assert( pModel );
    n_assert( pModel->IsValid() );

    //TODO- load source mesh data if not loaded
    //if ( pModel->IsLoaded() )
    //if ( !pModel->Load() ) return 0;

    float* pVertexData;
    uint vertexSize;
    if ( !this->ExportVertices( pModel, vertexSize, pVertexData ) ) return 0;

    void* pVertexDecl;
    if ( !this->InitVertexDeclaration( pModel, &pVertexDecl ) ) return 0;

    //TODO- handle 16-bit indices
    uint indexSize = sizeof(DWORD);
    uint* pIndexData = pModel->pIndices;
    //if ( !this->ExportIndices( pModel, indexSize, pIndexData ) ) return 0;

    KrawD3D9Model* pD3D9Model = n_new( KrawD3D9Model );

    if ( FAILED( pD3D9Model->LoadVertexDeclaration( pVertexDecl ) ) )
        return 0;

    if ( FAILED( pD3D9Model->LoadVertexBuffer( pModel->nVertices, vertexSize, pVertexData ) ) )
        return 0;

    if ( FAILED( pD3D9Model->LoadIndexBuffer( pModel->nIndices, indexSize, pIndexData ) ) )
        return 0;

    //TODO- create vertex declaration
    //TODO- destroy source mesh data
    //pModel->Unload();

    return (void*) pD3D9Model;
}

//-----------------------------------------------------------------------------

void KrawD3D9Material::Unload( void* pModelData )
{
    n_assert( pModelData );

    //TODO- call Release() for shared models
    KrawD3D9Model* pD3D9Model = (KrawD3D9Model*) pModelData;
    n_delete( pD3D9Model );
}

//-----------------------------------------------------------------------------

void KrawD3D9Material::UpdateTransform()
{
    D3DXMATRIXA16* matWorld = (D3DXMATRIXA16*) &this->mWorld;
    g_pd3d9Device->SetTransform( D3DTS_WORLD, matWorld );
}

//-----------------------------------------------------------------------------

uint KrawD3D9Material::GetNumPrimitives( uint nIndices, uint type )
{
    D3DPRIMITIVETYPE d3dPrimitiveType = (D3DPRIMITIVETYPE) type;
    switch ( d3dPrimitiveType )
    {
    case D3DPT_POINTLIST:
        return nIndices;
    case D3DPT_LINELIST:
        return nIndices / 2;
    case D3DPT_LINESTRIP:
        return nIndices - 1;
    case D3DPT_TRIANGLELIST:
        return nIndices / 3;
    case D3DPT_TRIANGLESTRIP:
        return nIndices - 2;
    case D3DPT_TRIANGLEFAN:
        return nIndices - 2;
    }

    return 0;
}

//-----------------------------------------------------------------------------

void KrawD3D9Material::Render( void* pModelData )
{
    if ( !pModelData ) return;

    //update world transform
    this->UpdateTransform();

    //TEMP!
    g_pd3d9Device->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );

    //this goes between calls to effect->Begin(), effect->End(), effect->BeginPass(), effect->EndPass();
    KrawD3D9Model* pD3D9Model = (KrawD3D9Model*) pModelData;

    //set the vertex stream source
    n_assert( SUCCEEDED( g_pd3d9Device->SetStreamSource( 0, pD3D9Model->pVertexBuffer, 0, pD3D9Model->stride ) ) )

    //change vertex declaration
    //TODO- check that it is different than the current one
    n_assert( SUCCEEDED( g_pd3d9Device->SetVertexDeclaration( pD3D9Model->pVertexDecl ) ) );

    //set index buffer
    n_assert( SUCCEEDED( g_pd3d9Device->SetIndices( pD3D9Model->pIndexBuffer ) ) );

    //draw
    //TODO- select a range of vertices and indices
    //TODO- nIndices current index range size
    uint numPrimitives = this->GetNumPrimitives( pD3D9Model->nIndices, pD3D9Model->primitiveType );
    g_pd3d9Device->DrawIndexedPrimitive( pD3D9Model->primitiveType,
        0, 0 /*firstVertex*/, pD3D9Model->nVertices /*nVertices*/, 0 /*firstIndex*/, numPrimitives );

    //TEMP!
    g_pd3d9Device->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );
}
